package chap_01_CompletableFuture.step_02_CompletableFuture;

import lombok.Getter;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Description 电商竞价案例demo
 * @Author shihan
 * @Date 2022/9/4 11:12
 * @Version 1.0
 */
public class CompletableStudy_03_MallDemo {

    static List<NetMall> list = Stream.of(
            new NetMall("jd"),
            new NetMall("dangdang"),
            new NetMall("taobao"),
            new NetMall("pingduoduo"),
            new NetMall("tmall")
    ).collect(Collectors.toList());

    /**
     * 【最差解】最原始的同步方式：不采用异步任务处理，一个一个的同步操作
     *
     * @param list
     * @param productName
     * @return
     */
    public static List<String> getPrice(List<NetMall> list, String productName) {
        /*ArrayList<String> result = new ArrayList<>();
        for (NetMall netMall : list) {
            double calcPrice = netMall.calcPrice(productName);
            result.add(productName + " 在 " + netMall.getNetMallName() + " 的售价为： " + calcPrice);
        }
        System.out.println(result);
        return result;*/
        return list.stream()
                .map(netMall -> String.format(
                        "《%s》in %s price is %.2f",
                        productName,
                        netMall.getNetMallName(),
                        netMall.calcPrice(productName))
                )
                .collect(Collectors.toList());
    }

    /**
     * 【次优解（了解即可）】采用异步任务处理（异步回调）：虽然用来异步回调的方式，但是还是要保证在主线程结束之前，把异步任务的数据给获取到并返回才行（还是需要阻塞）
     *
     * @param list
     * @param productName
     * @return
     */
    public static List<String> getPriceCallBack(List<NetMall> list, String productName) {
        List<String> result = new ArrayList<>();
        list.stream().forEach(netMall ->
                CompletableFuture.supplyAsync(() -> String.format(
                        "《%s》in %s price is %.2f",
                        productName,
                        netMall.getNetMallName(),
                        netMall.calcPrice(productName)))
                        .whenComplete((v, e) -> {
                            if (e == null) {
                                result.add(v);
                            }
                        })
                        .exceptionally(e -> null)
        );
        // 手动阻塞，为的是异步回调把结果给返回
        while (result.size() != 3) {
            try {
                // 延迟的原因是减少空转的阻塞等待
                // 还有就是延迟时间较短的话，会有volatile对象创建的三步错乱的情况产生，也就是虽然result的size增了，但是对象仍然为null没放进去
                // 延迟时间是 100 毫秒，就会出现 volatile 对象创建错乱的情况，200 毫秒就基本不会出现了
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            continue;
        }
        return result;
    }

    /**
     * 【最优解】采用异步任务处理：本质上还是阻塞等待，只是等待的时间变成 1/n 罢了
     *
     * @param list
     * @param productName
     * @return
     */
    public static List<String> getPriceCompletableFuture(List<NetMall> list, String productName) {
        // 不要被 stream 流给迷惑住了，在借助 stream 生成 completableFutureList 时
        // map 映射已经进行 CompletableFuture 对象的创建，即异步线程任务已经提交开始执行
        // collect 只是把创建的 CF 对象给收集返回罢了
        List<CompletableFuture<String>> completableFutureList = list.stream()
                .map(netMall -> CompletableFuture.supplyAsync(() -> String.format(
                        "《%s》in %s price is %.2f",
                        productName,
                        netMall.getNetMallName(),
                        netMall.calcPrice(productName))))
                .collect(Collectors.toList());
        // 这里的 map 映射，依次处理集合元素，调用并返回 join 的结果，join 依然是阻塞等待的方式获取返回值的
        // 其实完全可以自己接收 CF 对象，并依次调用 CF 对象的 join 获取结果并进行装填返回，只是 stream 方式更优雅一些
        return completableFutureList.stream().map(CompletableFuture::join).collect(Collectors.toList());
    }

    public static void main(String[] args) {
        long t1 = System.currentTimeMillis();

        //List<String> stringList = getPrice(list, "mysql");
        //List<String> stringList = getPriceCallBack(list, "mysql");
        List<String> stringList = getPriceCompletableFuture(list, "mysql");
        System.out.println(stringList);

        long t2 = System.currentTimeMillis();
        System.out.println("总耗时：" + (t2 - t1));
    }

}

class NetMall {

    @Getter
    private String netMallName;

    public NetMall(String netMallName) {
        this.netMallName = netMallName;
    }

    /**
     * 工具类，用来模拟实现一个产品的价格
     *
     * @param productName
     * @return
     */
    public double calcPrice(String productName) {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // char 和数值进行运算，会转换成 char 字符对应的数值码进行运算
        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }

}
